#include "argument.h"

#include "memory.h"
#include "module.h"
#include "threading.h"

struct ArgumentSerializer : JSONWrapper<ARGUMENTSINFO> {
  bool Save(const ARGUMENTSINFO& value) override {
    setString("module", value.mod());
    setHex("start", value.start);
    setHex("end", value.end);
    setHex("icount", value.instructioncount);
    setBool("manual", value.manual);
    return true;
  }

  bool Load(ARGUMENTSINFO& value) override {
    std::string mod;
    if (!getString("module", mod)) return false;
    value.modhash = ModHashFromName(mod.c_str());
    return getHex("start", value.start) && getHex("end", value.end) &&
           getBool("manual", value.manual) &&
           getHex("icount", value.instructioncount) && value.end >= value.start;
  }
};

struct Arguments : SerializableModuleRangeMap<LockArguments, ARGUMENTSINFO,
                                              ArgumentSerializer> {
  void AdjustValue(ARGUMENTSINFO& value) const override {
    auto base = ModBaseFromName(value.mod().c_str());
    value.start += base;
    value.end += base;
  }

 protected:
  const char* jsonKey() const override { return "arguments"; }

  ModuleRange makeKey(const ARGUMENTSINFO& value) const override {
    return ModuleRange(value.modhash, Range(value.start, value.end));
  }
};

static Arguments arguments;

bool ArgumentAdd(duint Start, duint End, bool Manual, duint InstructionCount) {
  // Make sure memory is readable
  if (!MemIsValidReadPtr(Start)) return false;

  // Fail if boundary exceeds module size
  auto moduleBase = ModBaseFromAddr(Start);

  if (moduleBase != ModBaseFromAddr(End)) return false;

  // Fail if 'Start' and 'End' are incompatible
  if (Start > End || ArgumentOverlaps(Start, End)) return false;

  ARGUMENTSINFO argument;
  argument.modhash = ModHashFromAddr(moduleBase);
  argument.start = Start - moduleBase;
  argument.end = End - moduleBase;
  argument.manual = Manual;
  argument.instructioncount = InstructionCount;

  return arguments.Add(argument);
}

bool ArgumentGet(duint Address, duint* Start, duint* End, duint* InstrCount) {
  ARGUMENTSINFO argument;
  if (!arguments.Get(Arguments::VaKey(Address, Address), argument))
    return false;
  arguments.AdjustValue(argument);
  if (Start) *Start = argument.start;
  if (End) *End = argument.end;
  if (InstrCount) *InstrCount = argument.instructioncount;
  return true;
}

bool ArgumentOverlaps(duint Start, duint End) {
  // A argument can't end before it begins
  if (Start > End) return false;
  return arguments.Contains(Arguments::VaKey(Start, End));
}

bool ArgumentDelete(duint Address) {
  return arguments.Delete(Arguments::VaKey(Address, Address));
}

void ArgumentDelRange(duint Start, duint End, bool DeleteManual) {
  // Should all arguments be deleted?
  // 0x00000000 - 0xFFFFFFFF
  if (Start == 0 && End == ~0) {
    ArgumentClear();
  } else {
    // The start and end address must be in the same module
    auto moduleBase = ModBaseFromAddr(Start);

    if (moduleBase != ModBaseFromAddr(End)) return;

    // Convert these to a relative offset
    Start -= moduleBase;
    End -= moduleBase;

    arguments.DeleteWhere([=](const ARGUMENTSINFO& value) {
      if (!DeleteManual && value.manual) return false;
      return value.end >= Start && value.start <= End;
    });
  }
}

void ArgumentCacheSave(JSON Root) { arguments.CacheSave(Root); }

void ArgumentCacheLoad(JSON Root) { arguments.CacheLoad(Root); }

void ArgumentClear() { arguments.Clear(); }

void ArgumentGetList(std::vector<ARGUMENTSINFO>& list) {
  arguments.GetList(list);
}

bool ArgumentGetInfo(duint Address, ARGUMENTSINFO& info) {
  return arguments.Get(Arguments::VaKey(Address, Address), info);
}

bool ArgumentEnum(ARGUMENTSINFO* List, size_t* Size) {
  return arguments.Enum(List, Size);
}